<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS  闭包的注意事项</title>
    <script>
       /*
       参考博客： https://www.jianshu.com/p/26c81fde22fb
       ！！！ 很明显这里对于闭包 和 前一篇博客还是有些区别
       闭包指的是：能够访问另一个函数作用域的变量的函数。
       清晰的讲：闭包就是一个函数，这个函数能够访问其他函数的作用域中的变量。
       在OC 和  swift 中闭包对于外部变量的捕获 相对于JS 还是有区别的
       */
       function outer(){
           var a = '变量1'
           var inner = function (){
               console.log(a)
           }
           return inner;
       }

       /*
       实际上，闭包是站在作用域的角度上来定义的，因为inner访问到outer作用域的变量，
       所以inner就是一个闭包函数。虽然定义很简单，但是有很多坑点，比如this指向、变量的作用域，
       稍微不注意可能就造成内存泄露。我们先把问题抛一边，思考一个问题：为什么闭包函数能够访问其他函数的作用域?
       从堆栈的角度看待js函数
　　    基本变量的值一般都是存在栈内存中，而对象类型的变量的值存储在堆内存中，栈内存存储对应空间地址。
       基本的数据类型: Number 、Boolean、Undefined、String、Null。 这一点对于OC 和 swift 也是一样的 

       */
       var a = 1    // a是一个基本类型  
       var b = {m: 20}  // b是一个对象

       /*
               栈                   堆
        变量名     值               
                                     {m:20}
           a      1 
           b      0x0000000

        其中 0x0000000 对应 {m:20} 的堆中的地址
        当我们执行 b={m:30}时，堆内存就有新的对象{m：30}，栈内存的b指向新的空间地址( 指向{m：30} )，
        而堆内存中原来的{m：20}就会被程序引擎垃圾回收掉
        ，节约内存空间。我们知道js函数也是对象，它也是在堆与栈内存中存储的
       */

       var c = 1
       function fn(){
           var d = 2;
           function fn1(){
               console.log(d)
           }
           fn1()
       }
       fn()

       /*
       下面我们分析一下上面函数的过程
                栈
             fn1 执行环境
        变量名            值
                无
        ----------------------
             fn执行环境
        d                2
        fn1             0x000001
        -------------------------
             全局执行环境
        a                 1
        fn              引用地址
       ---------------------------
       栈是一种先进后出的数据结构：
        1 在执行fn前，此时我们在全局执行环境(浏览器就是window作用域)，全局作用域里有个变量a；
        2 进入fn，此时栈内存就会push一个fn的执行环境，这个环境里有变量b和函数对象fn1，这里可以访问自身执行环境和全局执行环境所定义的变量
        3 进入fn1，此时栈内存就会push 一个fn1的执行环境，这里面没有定义其他变量，但是我们可以访问到fn和全局执行环境里面的变量，
        因为程序在访问变量时，是向底层栈一个个找，如果找到全局执行环境里都没有对应变量，则程序抛出underfined的错误。

        4 随着fn1()执行完毕，fn1的执行环境被杯销毁，接着执行完fn()，fn的执行环境也会被销毁，只剩全局的执行环境下，现在没有b变量，
        和fn1函数对象了，只有a 和 fn(函数声明作用域是window下)
        在函数内访问某个变量是根据函数作用域链来判断变量是否存在的，而函数作用域链是程序根据函数所在的执行环境栈来初始化的，
        所以上面的例子，我们在fn1里面打印变量b，根据fn1的作用域链的找到对应fn执行环境下的变量b。所以当程序在调用某个函数时，做了一下的工作：
        准备执行环境，初始函数作用域链和arguments参数对象

        我们现在看回最初的例子outer与inner
        当程序执行完var inner = outer()，其实outer的执行环境并没有被销毁，因为他里面的变量a仍然被被inner的函数作用域链所引用，
        当程序执行完inner(), 这时候，inner和outer的执行环境才会被销毁调；《JavaScript高级编程》书中建议：
        由于闭包会携带包含它的函数的作用域，因为会比其他函数占用更多内容，过度使用闭包，会导致内存占用过多。
       */
       var inner = outer()
       inner()

       /*
       现在我们明白了闭包，已经对应的作用域与作用域链，回归主题：
       坑点1： 引用的变量可能发生变化
       */
       function outerOne(){
        //    var result = []
           for (var i=0; i<10; i++){
               result = function (){
                  console.log(i)
               }
           }
           return result
       }
       
       var resultOne = outerOne()
       resultOne()   // 10 
       resultOne()   // 10
       resultOne()  // 都是打印 10 为啥不是 0 1，2，3 ....9

       /*
       看样子result每个闭包函数对打印对应数字，0,2,3,4,...,9, 实际不是，
       因为每个闭包函数访问变量i是outer执行环境下的变量i，随着循环的结束，i已经变成10了，
       所以执行每个闭包函数，结果打印10， 10， ..., 10
       怎么解决这个问题
       */
       function outerTwo(){
           var result = []
           for (var j = 0; j<10; j++){
               result[j] = function (num) {  // 此时访问的num，是上层函数执行环境的num，数组有10个函数对象，每个对象的执行环境下的number都不一样
                   return function(){
                       console.log(num)
                   }(j)
               }
           }
           return result
       }

       var resultTwo = outerTwo()
    //    alert(resultTwo)
    // for(var q=0;q<10;q++){
    //     fuc = resultTwo[q]
    //     fuc()
    // }

    //    resultTwo[0]()
    //    resultTwo[1]()
    //    resultTwo[9]()
    </script>
</head>
<body>
    <button onclick="testCommonUseMethod()">测试常用的方法</button><br/>  <!-- 测试常用的方法 -->
</body>
</html>